#!/usr/bin/env python2.5
#coding: utf8

#Author: Gang Li
#Date: 2010-04-10 18:20

import logging
import re
from os.path import split, join
import socket, select

from core.utils import *
from core.transport import Server 

class FSFile():
    @staticmethod
    def open(name, mode):
        p, n = split(name)
        dir = FSDir.get(p)
        file = open("%s/%s" % (dir.get_abosulte(), n), mode)
        return FSFile(name=n, parent=dir, file=file)
        
    def __init__(self, name, parent, file=None, skt=None):
        if name.startswith("/"):
            p, n = split(name)
            self.name = n
            
            if parent: parent = FSDir.get(p)
        else:
            self.name = name
            if not parent: parent = FSDir.get("/")

        self.parent = parent
        self.file = file
        self.skt = skt
        self.register()

    def register(self):
        fs.register(self)

    def read(self, amt):
        s = ''
        while amt > 0:
            chunk = self.file.recv(amt)
            if not chunk:
                return None
            s = s + chunk
            amt = amt - len(chunk)
        return s

    def close(self):
        self.file.close()

    def delelte(self):
        pass

class FSRoot(dict):
    @staticmethod
    def open(root="/tmp", handler=None, mode="meta"):
        fs = FSRoot(handler, mode)
        fs['/'] = FSDir(root=root, fs=fs)
        return fs

    def get_root(self):
        return self["/"]

    def __init__(self, handler, mode):
        """
            start file transfer server
        """
        self.handler = handler
        self.port = 2000

        while 1:
            try:
                self.server = Server("0.0.0.0", self.port, self)
                print "fs serving at ", self.server.sock.getsockname()
                break
            except BaseException, msg:
                print msg
                self.port += 1

        self.map = {}
        self.mode = mode

    def register(self, obj):
        if self.mode is "meta":
            logging.info("reg %s: %s" % (type(obj), obj.name))
        else:
            m = {"name":obj.name, "port":self.port}
            if isinstance(obj, FSFile):
                self.handler and self.handler.reg_file(m)
            elif isinstance(obj, FSDir):
                self.handler and self.handler.reg_dir(m)

    def close(self):
        self.server.close()

    def connected(self, skt):
        print "new con", skt

    def disconnected(self, skt):
        print "new con", skt

    def on_cmd(self, skt, id, data):
        print "fs cmd:" , id, data

        #read data
        if id == READ_FILE:
            #user data
            print "read file"
            try:
                file = FSFile.open(data, 'rb')
                self.writes.append(skt)
                self.map[skt] = file
                self.server.touch_write(skt, lambda :self.map[skt].read(8192), lambda :skt.close())
            except BaseException, msg:
                print "read file cmd:", msg
                pass
        #FIXME
        self.map[skt] = None

class FSDir():

    def __init__(self, name="/", parent=None, root="/tmp", fs=None):

        if name in (hasattr(self, "fs") and self.fs or fs):
            raise Exception("dir '%s' already exists" % name)

        if fs is not None:
            self.__class__.fs = fs

        self.__class__.root = root

        self.name = name

        self.parent = parent
        self.fs[name] = self
        
        self.files = []
        
        self.register()

    def register(self):
        self.fs.register(self)

    @staticmethod
    def get(name="/"):
        if name in self.fs:
            return self.fs[name]
        raise Exception("dir '%s' not exists" % name)

    def get_abosulte(self):
        if self.root:
            return self.root + (self.root.endswith("/") and self.name[1:] or self.name)

        raise Exception("file system's root is not setted.")

    def mkdir(self, dir):
        """
            add a new dir to an exist directory
        """

        if not dir: return self

        ds = self._join(dir)
        rd = "/" + "/".join(ds)

        if rd in self.fs:
            return self.fs[rd]

        rd_p = "/" + "/".join(ds[:-1])

        if rd_p in self.fs:
            #parent is exists
            return FSDir(parent=self.fs[rd_p], name=rd)

        #parent is not exists
        #or
        #parent is in another dir, need lookup it's parent

    def _join(self, dir):
        absolute = dir.startswith("/")
        if absolute:
            ds = [d for d in dir.split("/") if d]
        else:
            _ds = [d for d in re.split(r"[\.|/]", dir) if d]
            ds = [d for d in self.name.split("/") if d]
            ds.extend(_ds)

        return ds or []
  
    def mkdirs(self, dir):
        if not dir: return self

        rd = "/"
        _d = None
        for d in self._join(dir):
            rd += (d + "/")
            _d = self.mkdir(rd)

        return _d

    def rmdir(self, dir):
        """
        目录非空不允许删除
        """
        pass
    def rmdirs(self, dir):
        """
        目录非空也允许删除
        """
        pass

    def addfile(self, file):
        path, name = split(file.name)
        p = self.mkdirs(path)
        p.files.append(file)

    def rmfile(self, file_name):
        pass

    def __cmp__(self, another):
        return self.name == another.name and self.parent == self.parent


import unittest
class FSTest(unittest.TestCase):
    def testFSDir_mkdir(self):
        fs = FSRoot.open()
        root = fs.get_root()
        self.assertEqual("/a/b", root.mkdir("/a/").mkdir("/a/b").name)
        self.assertEqual("/a/b/c", root.mkdir("/a//b/c").name)

        self.assertEqual("/a/b", root.mkdir("/a/").mkdir("/a/b").name)
        self.assertEqual("/d", root.mkdir("/a/").mkdir("/d").name)
        self.assertEqual("/a/b", root.mkdir("/a/").mkdir("/d").mkdir("/a/b").name)
        self.assertEqual("/a", root.mkdir("/a").mkdir("/d").mkdir("/a/b").parent.name)
        self.assertEqual("/", root.mkdir("/a/").mkdir("/d").mkdir("/").name)
        self.assertEqual("/a/d", root.mkdir("/a").mkdir("./d").name)
        self.assertEqual(None, root.mkdir("/a/").mkdir("./dd/c"))

        self.assertEqual("/a/dd", root.mkdir("/a/").mkdir("./dd").name)
        self.assertEqual("/a/dd/c", root.mkdir("/a/dd/c").name)

        self.assertEqual("/", root.mkdir("").name)
        self.assertEqual("/a/b", root.mkdir("/a/b").mkdir("").name)
        self.assertEqual("/a/b", root.mkdir("/a/b").mkdir(None).name)

        self.assertEqual(id(FSDir.get("/a/b")), id(root.mkdir("/a/b").mkdir(None)))

    def testFSDir_mkdirs(self):
        fs = FSRoot.open()
        root = fs.get_root()
        self.assertEqual(None, root.mkdir("/a/cc/bb/ss/v"))
        self.assertEqual("/a/cc/bb/ss/v", root.mkdirs("/a/cc/bb/ss/v").name)
        self.assertEqual("/a/b", root.mkdirs("/a/b").mkdirs("").name)
        self.assertEqual("/a/b", root.mkdirs("/a/b").mkdirs(None).name)
        self.assertEqual("/", root.mkdirs("").name)

    def testFSDir_new(self):
        fs = FSRoot.open()
        root = fs.get_root()
        d = root.mkdir("/a")
        self.assertEqual(FSDir.get("/a"), d)
        self.assertEqual("/a/c", FSDir.get("/a").mkdir('c').name)

    def testFSDir_add_file(self):
        fs = FSRoot.open()
        root = fs.get_root()
        d = root.mkdir("/a")
        d.addfile(FSFile("test.txt", d))
        self.assertEqual(1, len(FSDir.get("/a").files))
        self.assertEqual("test.txt", FSDir.get("/a").files[0].name)

    def testFSDir_add_file_2(self):
        fs = FSRoot.open()
        root = fs.get_root()
        d = root.mkdir("/b")
        d.mkdirs("./b/c/fds/x").addfile(FSFile("/b/test.txt", d))
        self.assertEqual(1, len(FSDir.get("/b").files))
        self.assertEqual("/b/test.txt", FSDir.get("/b").files[0].name)

    def testFSDir_root(self):
        self.assertEqual("/tmp", FSDir.get("/a").root)
        self.assertEqual("/tmp", FSDir.get("/b").root)
        self.assertEqual("/tmp/b", FSDir.get("/b").get_abosulte())
        
if __name__ == "__main__":
    unittest.main()
